package com.gitee.hermer.engine.jars.core.loader;


import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.collections.MultiHashMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import com.gitee.hermer.boot.jee.commons.collection.CollectionUtils;
import com.gitee.hermer.boot.jee.commons.constants.OSType;
import com.gitee.hermer.boot.jee.commons.exception.ErrorCode;
import com.gitee.hermer.boot.jee.commons.exception.PaiUException;
import com.gitee.hermer.boot.jee.commons.log.UtilsContext;
import com.gitee.hermer.boot.jee.commons.utils.FileUtils;
import com.gitee.hermer.boot.jee.commons.utils.JsonUtils;
import com.gitee.hermer.boot.jee.commons.utils.OSUtils;
import com.gitee.hermer.boot.jee.commons.verify.Assert;
import com.gitee.hermer.engine.jars.app.sdk.EngineAppClassLoader;
import com.gitee.hermer.engine.jars.core.app.domain.EngineAppContext;
import com.gitee.hermer.engine.jars.core.context.IEngineAppContext;
import com.gitee.hermer.engine.jars.core.context.impl.EngineAppContextImpl.EngineAppLoaderState;
import com.gitee.hermer.engine.jars.core.domain.EngineConstant;
import com.gitee.hermer.engine.jars.core.loader.event.EngineAppInitializationEvent;
import com.gitee.hermer.engine.jars.dependent.dsm.configuration.PlaceholderConfigurerAutoConfiguration;

@Component
@ComponentScan("com.gitee.hermer.engine.jars.dependent.dsm")
public class EngineAppBeanScanner extends UtilsContext implements InitializingBean,IEngineAppClassLoaderCacheMap{


	@Autowired
	private IEngineAppContext engineAppContext;

	public static String[] getFilePaths(File[] file){
		List<String> array = CollectionUtils.newArrayList();
		for (File f : file) {
			array.add(f.getPath());
		}
		return array.toArray(new String[]{});
	}


	public static File getMonitorWorkLibs(){
		return getMonitorWorkDirectory(EngineConstant.FILE_NAME_ENGINE_WORK_LIB);
	}

	public static File getMonitorWorkConfs(){
		return getMonitorWorkDirectory(EngineConstant.FILE_NAME_ENGINE_WORK_CONF);
	}

	public static File getMonitorWorkJars(){
		return getMonitorWorkDirectory(EngineConstant.FILE_NAME_ENGINE_WORK_JAR);
	}

	public static File[] getMonitorWorkJarFiles(){
		return getMonitorWorkJars().listFiles(new JarFilenameFilter());
	}

	public static File[] getMonitorWorkLibFiles(){
		return getMonitorWorkLibs().listFiles(new JarFilenameFilter());
	}
	
	public static File getMonitorWorkJarFiles(String fileName){
		return new File(getMonitorWorkJars().getPath()+File.separator+fileName);
	}

	static final class JarFilenameFilter implements FilenameFilter{

		@Override
		public boolean accept(File dir, String name) {
			return org.apache.commons.lang3.StringUtils.endsWith(name, "jar");
		}

	}

	public static String getWebInfPath(){
		URL root = Thread.currentThread().getContextClassLoader().getResource("/");
		if(root == null)
			root = EngineAppBeanScanner.class.getResource("/");
		String path = root.getPath();
		Integer index = path.indexOf("test-classes");
		index = index == -1?path.indexOf("classes"):index;
		if(OSUtils.getOSname() == OSType.Windows){
			return path.substring(1, index);
		}else{
			return path.substring(0, index);
		}
	}

	public static File getMonitorWorkDirectory(String name){
		String path = getWebInfPath()+name+File.separator;
		File file = new File(path);
		if(!file.exists())
			file.mkdirs();
		return file;
	}


	public EngineAppClassLoader getClassLoader(File jarFile,ClassLoader loader,String cacheKey) throws IOException{
		return getEngineAppClassLoader(jarFile.getPath(),loader,getFilePaths(getMonitorWorkLibFiles()),cacheKey);
	}

	/**
	 * 初始化JarClassloader
	 * @param jarUrl
	 * @param dependencyUrls
	 * @return
	 * @throws IOException 
	 */
	public  EngineAppClassLoader getEngineAppClassLoader(String jarUrl,ClassLoader parent,String[] dependencyUrls,String cacheKey) throws IOException {

		EngineAppClassLoader classLoader = get(cacheKey);
		if(classLoader == null){

			List<String> filePaths = CollectionUtils.newArrayList(dependencyUrls == null?new String[]{}:dependencyUrls);
			filePaths.add(jarUrl);
			List<URL> jarUrls = CollectionUtils.newArrayList();
			for (String url : filePaths) {
				jarUrls.add(new URL("file:"+url));
			}
			classLoader = new EngineAppClassLoader(jarUrls.toArray(new URL[]{}),parent,jarUrl,cacheKey,findResources(filePaths));
			push(cacheKey, classLoader);
			return classLoader;
		}else
			return classLoader;
	}

	private static MultiHashMap findResources(List<String> paths) throws IOException{
		MultiHashMap hashMap = new MultiHashMap();
		for (String jarUrl : paths) {
			JarFile localJarFile = new JarFile(new File(jarUrl));
			Enumeration<JarEntry> entries = localJarFile.entries();
			while (entries.hasMoreElements()) {
				JarEntry jarEntry = entries.nextElement();
				if(EngineConstant.DEFAULT_VALUES_ENGINE_APP_SCAN_RESOURCES.contains(FileUtils.getExtension(jarEntry.getName()))){
					hashMap.put(jarUrl, jarEntry.getName());
				}
			}
		}
		return hashMap;
	}

	public ApplicationContext springClassLoader(EngineAppClassLoader loader) throws Throwable{
		engineAppContext.updateEngineAppLoaderState(loader.getCacheKey(), EngineAppLoaderState.INIT_ING);
		info(String.format("扫描作业线程ID[%s]|线程名[%s]", Thread.currentThread().getId(),Thread.currentThread().getName()));
		if(!Thread.currentThread().getContextClassLoader().equals(loader))
			Thread.currentThread().setContextClassLoader(loader);
		String configurationFilePath = loader.getJarResourcesUrl("application-config.xml");
		ApplicationContext ctx = new ClassPathXmlApplicationContext(configurationFilePath);
		EngineAppContext context = ctx.getBean(EngineAppContext.class);
		info(String.format("扫描到容器Context：[%s]",JsonUtils.toJSONString(context.getEngineBeans())));
		return ctx;
	}

	public static EngineAppInitializationEvent  createEngineAppInitializationEvent(File jar) throws IOException{
		InputStream stream = null;
		InputStreamReader reader = null;
		JarFile jarFile = null;
		try{
			jarFile = new JarFile(jar);
			JarEntry entry = jarFile.getJarEntry(EngineConstant.SYSTEM_CONFIG_PROPERTIES_FILE_NAME);
			reader = new InputStreamReader(jarFile.getInputStream(entry));
			Properties prop = new Properties();
			prop.load(reader);
			return new EngineAppInitializationEvent(jar,prop.getProperty(EngineConstant.PROPERTIES_VALUE_KEY_APP_MODEL_KEY),
					(String)prop.getOrDefault(EngineConstant.PROPERTIES_VALUE_KEY_APP_VERSION_KEY, "0.0.1"));
		}catch (IOException e) {
			throw new PaiUException(ErrorCode.SYSTEM_ERROR,e,String.format("文件[%s]没检测在%s到[com.jc.app]属性...初始化失败！", jar.getName(),EngineConstant.SYSTEM_CONFIG_PROPERTIES_FILE_NAME));
		}finally {
			if(stream != null){
				stream.close();
			}
			if(reader != null){
				reader.close();
			}
			if(jarFile != null){
				jarFile.close();
			}
		}

	}


	@Override
	public void afterPropertiesSet() throws Exception {
		getMonitorWorkJars();
		getMonitorWorkLibs();
		getMonitorWorkConfs();
	}


	@Override
	public EngineAppClassLoader get(String cacheKey) {
		EngineAppClassLoader classLoader = CACHE_LOADER.get(cacheKey);
		if(classLoader != null && classLoader.isClose()){
			remove(cacheKey);
			return null;
		}
		return classLoader;
	}


	@Override
	public EngineAppClassLoader remove(String cacheKey) {
		return CACHE_LOADER.remove(cacheKey);
	}


	@Override
	public void push(String cacheKey, EngineAppClassLoader appClassLoader) {
		Assert.isNull(get(cacheKey));
		CACHE_LOADER.put(cacheKey, appClassLoader);
	}



}
